package com.koushikdutta.async.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Provider.Service;
import java.security.Security;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Random;

public class FileCache {
    private static String hashAlgorithm = "MD5";
    long blockSize = 4096;
    InternalCache cache;
    Comparator<File> dateCompare = new Comparator<File>() {
        public int compare(File lhs, File rhs) {
            long l = lhs.lastModified();
            long r = rhs.lastModified();
            if (l < r) {
                return -1;
            }
            if (r > l) {
                return 1;
            }
            return 0;
        }
    };
    File directory;
    boolean loadAsync;
    boolean loading;
    Random random = new Random();
    long size;

    class CacheEntry {
        final long size;

        public CacheEntry(File file) {
            this.size = file.length();
        }
    }

    class InternalCache extends LruCache<String, CacheEntry> {
        public InternalCache() {
            super(FileCache.this.size);
        }

        protected long sizeOf(String key, CacheEntry value) {
            return Math.max(FileCache.this.blockSize, value.size);
        }

        protected void entryRemoved(boolean evicted, String key, CacheEntry oldValue, CacheEntry newValue) {
            super.entryRemoved(evicted, key, oldValue, newValue);
            if (newValue == null && !FileCache.this.loading) {
                new File(FileCache.this.directory, key).delete();
            }
        }
    }

    private static MessageDigest findAlternativeMessageDigest() {
        if ("MD5".equals(hashAlgorithm)) {
            for (Provider provider : Security.getProviders()) {
                for (Service service : provider.getServices()) {
                    hashAlgorithm = service.getAlgorithm();
                    try {
                        MessageDigest messageDigest = MessageDigest.getInstance(hashAlgorithm);
                        if (messageDigest != null) {
                            return messageDigest;
                        }
                    } catch (NoSuchAlgorithmException e) {
                    }
                }
            }
        }
        return null;
    }

    public static String toKeyString(Object... parts) {
        MessageDigest messageDigest;
        synchronized (FileCache.class) {
            try {
                messageDigest = MessageDigest.getInstance(hashAlgorithm);
            } catch (NoSuchAlgorithmException e) {
                messageDigest = findAlternativeMessageDigest();
                if (messageDigest == null) {
                    throw new RuntimeException(e);
                }
            }
        }
        for (Object part : parts) {
            messageDigest.update(part.toString().getBytes());
        }
        return new BigInteger(1, messageDigest.digest()).toString(16);
    }

    public File getTempFile() {
        File f;
        do {
            f = new File(this.directory, new BigInteger(128, this.random).toString(16));
        } while (f.exists());
        return f;
    }

    public File[] getTempFiles(int count) {
        File[] ret = new File[count];
        for (int i = 0; i < count; i++) {
            ret[i] = getTempFile();
        }
        return ret;
    }

    public static void removeFiles(File... files) {
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
    }

    public void remove(String key) {
        for (int i = 0; this.cache.remove(getPartName(key, i)) != null; i++) {
        }
        removePartFiles(key);
    }

    public boolean exists(String key) {
        return getPartFile(key, 0).exists();
    }

    public File touch(File file) {
        this.cache.get(file.getName());
        file.setLastModified(System.currentTimeMillis());
        return file;
    }

    public FileInputStream get(String key) throws IOException {
        return new FileInputStream(touch(getPartFile(key, 0)));
    }

    public File getFile(String key) {
        return touch(getPartFile(key, 0));
    }

    public FileInputStream[] get(String key, int count) throws IOException {
        FileInputStream[] ret = new FileInputStream[count];
        int i = 0;
        while (i < count) {
            try {
                ret[i] = new FileInputStream(touch(getPartFile(key, i)));
                i++;
            } catch (IOException e) {
                int len$ = ret.length;
                for (int i$ = 0; i$ < len$; i$++) {
                    StreamUtility.closeQuietly(arr$[i$]);
                }
                remove(key);
                throw e;
            }
        }
        return ret;
    }

    String getPartName(String key, int part) {
        return key + "." + part;
    }

    public void commitTempFiles(String key, File... tempFiles) {
        removePartFiles(key);
        int i = 0;
        while (i < tempFiles.length) {
            File tmp = tempFiles[i];
            File partFile = getPartFile(key, i);
            if (tmp.renameTo(partFile)) {
                remove(tmp.getName());
                this.cache.put(getPartName(key, i), new CacheEntry(partFile));
                i++;
            } else {
                removeFiles(tempFiles);
                remove(key);
                return;
            }
        }
    }

    void removePartFiles(String key) {
        int i = 0;
        while (true) {
            File f = getPartFile(key, i);
            if (f.exists()) {
                f.delete();
                i++;
            } else {
                return;
            }
        }
    }

    File getPartFile(String key, int part) {
        return new File(this.directory, getPartName(key, part));
    }

    void load() {
        this.loading = true;
        try {
            File[] files = this.directory.listFiles();
            if (files != null) {
                ArrayList<File> list = new ArrayList();
                Collections.addAll(list, files);
                Collections.sort(list, this.dateCompare);
                Iterator i$ = list.iterator();
                while (i$.hasNext()) {
                    File file = (File) i$.next();
                    String name = file.getName();
                    this.cache.put(name, new CacheEntry(file));
                    this.cache.get(name);
                }
                this.loading = false;
            }
        } finally {
            this.loading = false;
        }
    }

    private void doLoad() {
        if (this.loadAsync) {
            new Thread() {
                public void run() {
                    FileCache.this.load();
                }
            }.start();
        } else {
            load();
        }
    }

    public FileCache(File directory, long size, boolean loadAsync) {
        this.directory = directory;
        this.size = size;
        this.loadAsync = loadAsync;
        this.cache = new InternalCache();
        directory.mkdirs();
        doLoad();
    }
}
